package math.dataset;

/**
 * Useful class for doing simple statistical analysis of data.
 * Note all methods are implemented such that calculations will only need
 * to be performed once, then subsequent calls to the same method will not
 * involve any calculations as the results are stored. For example,
 * the getMeanX() method will calculate the mean of the x values the first
 * time it is called, but the result will be stored in an instance variable.
 * Hence, the next time getMeanX() is called, the stored result is returned
 * without needing to recalculate. These instance variables are set to
 * null during instantiation, and the methods check if an instance variable
 * is null then performs calculations if it is, otherwise it'll simply
 * return the value of the instance variable. All methods in this class
 * are implemented this way.
 * 
 * @author Shimu
 */
public class DataSet_2V extends DataSet_Float {

    private float[] dataX, dataY;

    private int size;

    private Float meanX, medianX, minX, maxX, varianceX, standardDeviationX,
            meanY, medianY, minY, maxY, varianceY, standardDeviationY,
            correlation;

    private boolean sortedX, sortedY;

    public DataSet_2V(float[] dataX, float[] dataY) throws DataSetException {
        if ((dataX == null) || (dataY == null)) {
            throw new DataSetException("dataX and/or dataY is empty!");
        } else if (dataX.length != dataY.length) {
            throw new DataSetException("data array X different in length from"
                    + " dataSetY!");
        }

        this.dataX = dataX;
        this.size = dataX.length;
        this.meanX = null;
        this.medianX = null;
        this.minX = null;
        this.maxX = null;
        this.varianceX = null;
        this.sortedX = false;

        this.dataY = dataY;
        this.meanY = null;
        this.medianY = null;
        this.minY = null;
        this.maxY = null;
        this.varianceY = null;
        this.sortedY = false;

        this.correlation = null;
    }

    /**
     * Return the size of the dataset.
     * @return the size of the dataset.
     */
    public int getSize() {
        return size;
    }

    /**
     * Returns the X mean (average) of this dataset, null if the dataset is
     * empty. Uses DataSet's getMean(float[]) method.
     *
     * @see DataSet.getMean(float[])
     * @return the X mean (average) of this dataset, null if the dataset
     * is empty.
     */
    public Float getMeanX() {
        if (this.meanX == null) {
            meanX = getMean(this.dataX);
        }
        return meanX;
    }

    /**
     * Returns the Y mean (average) of this dataset, null if the dataset is
     * empty. Uses DataSet's getMean(float[]) method.
     *
     * @see DataSet.getMean(float[])
     * @return the Y mean (average) of this dataset, null if the dataset is
     * empty.
     */
    public Float getMeanY() {
        if (this.meanY == null) {
            meanY = getMean(this.dataY);
        }
        return meanY;
    }

    /**
     * Returns the X median (middle number) of this dataset, null if the
     * dataset is empty. Uses DataSet's getMedian(float[]) method
     *
     * @see DataSet.getMedian(boolean, float[])
     * @return the X median of this dataset, null if the dataset is empty.
     */
    public Float getMedianX() {
        if (this.medianX == null) {
            medianX = getMedian(sortedX, dataX);
        }
        return medianX;
    }

    /**
     * Returns the Y median (middle number) of this dataset, null if the
     * dataset is empty. Uses DataSet's getMedian(float[]) method
     *
     * @see DataSet.getMedian(boolean, float[])
     * @return the Y median of this dataset.
     */
    public Float getMedianY() {
        if (this.medianY == null) {
            medianY = getMedian(sortedY, dataY);
        }
        return medianY;
    }

    /**
     * Returns the minimum X value of this dataset, null if the dataset is
     * empty. Uses DataSet's getMin(boolean, float[]) method.
     *
     * @see DataSet.getMin(boolean, float[])
     * @return the minimum X value of this dataset, null if the dataset is
     * empty.
     */
    public Float getMinX() {
        if (this.minX == null) {
            //this.minX = getMin(sortedX, dataX);
            this.minX = getMin(dataX);
        }
        return minX;
    }

    /**
     * Returns the minimum Y value of this dataset, null if the dataset is
     * empty. Uses DataSet's getMin(boolean, float[]) method.
     *
     * @see DataSet.getMin(boolean, float[])
     * @return the minimum Y value of this dataset, null if the dataset is
     * empty.
     */
    public Float getMinY() {
        if (this.minY == null) {
            //this.minY = getMin(sortedY, dataY);
            this.minY = getMin(dataY);
        }
        return minY;
    }

    /**
     * Returns the maximum X value of this dataset, null if the dataset is
     * empty. Uses DataSet's getMax(boolean, float[]) method.
     *
     * @see DataSet.getMax(boolean, float[])
     * @return the maximum X value of this dataset, null if the dataset is
     * empty.
     */
    public Float getMaxX() {
        if (this.maxX == null) {
            //this.maxX = getMax(sortedX, dataX);
            this.maxX = getMax(dataX);
        }
        return maxX;
    }

    /**
     * Returns the maximum Y value of this dataset, null if the dataset is
     * empty. Uses DataSet's getMax(boolean, float[]) method.
     *
     * @see DataSet.getMax(boolean, float[])
     * @return the maximum Y value of this dataset, null if the dataset is
     * empty.
     */
    public Float getMaxY() {
        if (this.maxY == null) {
            //this.maxY = getMax(sortedY, dataY);
            this.maxY = getMax(dataY);
        }
        return maxY;
    }

    /**
     * Returns the X variance of this dataset, null if the data is empty.
     * Uses DataSet's getVariance(float[], float) method.
     *
     * @see DataSet.getVariance(float[], float)
     * @return the X variance of the dataset, null if the data is empty.
     */
    public Float getVarianceX() {
        if (this.varianceX == null) {
            this.varianceX = getVariance(dataX, getMeanX());
        }
        return varianceX;
    }

    /**
     * Returns the Y variance of this dataset, null if the data is empty.
     * Uses DataSet's getVariance(float[], float) method.
     *
     * @see DataSet.getVariance(float[], float)
     * @return the Y variance of the dataset, null if the data is empty.
     */
    public Float getVarianceY() {
        if (this.varianceY == null) {
            this.varianceY = getVariance(dataY, getMeanY());
        }
        return varianceY;
    }

    /**
     * Returns the X standard deviation of this dataset, null if the data is
     * empty, 0 if the data contains only one value. Uses DataSet's
     * getStandardDeviation(float) method.
     *
     * @see DataSet.getStandardDeviation(float)
     * @return the X standard deviation of this dataset, null if the data is
     * empty, 0 if the data contains only one value. 
     */
    public Float getStandardDeviationX() {
        if (this.standardDeviationX == null) {
            this.standardDeviationX = getStandardDeviation(getVarianceX());
        }
        return standardDeviationX;
    }

    /**
     * Returns the Y standard deviation of this dataset, null if the data is
     * empty, 0 if the data contains only one value. Uses DataSet's
     * getStandardDeviation(float) method.
     *
     * @see DataSet.getStandardDeviation(float)
     * @return the Y standard deviation of this dataset, null if the data is
     * empty, 0 if the data contains only one value.
     */
    public Float getStandardDeviationY() {
        if (this.standardDeviationY == null) {
            this.standardDeviationY = getStandardDeviation(getVarianceY());
        }
        return standardDeviationY;
    }

    public Float getCorrelation() {
        if (this.correlation == null) {
            if (this.size == 0) {
                this.correlation = null;
            } else if (this.size == 1) {
                this.correlation = 1f;
            } else if ((standardDeviationX == 0) || (standardDeviationY == 0)) {
                this.correlation = 0f;
            } else {
                float sum = 0;
                for (int i = 0; i < size; i++) {
                    sum += ((dataX[i] - meanX) / standardDeviationX)
                            * ((dataY[i] - meanY) / standardDeviationY);
                }
                this.correlation = sum / (size - 1);
            }
        }
        return correlation;
    }
}
